Skip to content

feat(interpreter): child shells count SHLVL like bash - #2622

Merged
chaliy merged 1 commit into
mainfrom
claude/shlvl-var
Oct 8, 2026
Merged

chaliy merged 1 commit into
mainfrom
claude/shlvl-var

Conversation

@chaliy

@chaliy chaliy commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Requested by Михайло · project thread

What changed

  • bash/sh child shells count $SHLVL the way bash's adjust_shell_level does: inherited level + 1, non-numeric counts as 0, negative lands on 0, past 999 restarts at 1 with bash's shell level (N) too high warning. The value is exported, so grandchildren and env see it.
  • A child shell's output no longer streams past the bash command's own output redirects.
  • The spec comparison runs real bash without the test runner's own SHLVL, since a bashkit script is a top-level shell.

Why

Bash-oracle gap shlvl-nesting, and a real leak: bash -c 'echo leak >&2' 2>/dev/null printed leak in streaming mode.

Before / After

$ bashkit -c 'echo $SHLVL; bash -c "echo \$SHLVL; bash -c \"echo \\\$SHLVL; true\"; true"'
before: 1 1 1        after: 1 2 3     (bash: 1 2 3)
$ SHLVL=999 bash -c 'echo $SHLVL; true'
before: 999          after: warning + 1   (bash: warning + 1)

8 spec cases (verified with env -u SHLVL bash and by bash_comparison_tests) plus integration tests for the warning, its redirection and streaming.

Risk

Low. bash's fork suppression (a sole bash -c command execs without raising the level) is not modeled; documented in L-PROC-003.

Checklist

  • fmt, clippy -D warnings
  • integration, lib, cli tests; bash_comparison_tests green
  • knowledge updated (log.md, limitations L-PROC-003)

Generated by Claude Code

@chaliy chaliy self-assigned this Oct 8, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 7e574b6 Commit Preview URL

Branch Preview URL
Oct 08 2026, 03:22 AM

bash and sh children raise the inherited SHLVL by one, apply bash's
non-numeric, negative and >999 rules (with its warning), and export it.
A child's output no longer streams past the bash command's own output
redirects. The spec comparison runs real bash without the runner's SHLVL.
@chaliy
chaliy merged commit e8ae018 into main Oct 8, 2026
46 checks passed
@chaliy
chaliy deleted the claude/shlvl-var branch October 8, 2026 03:50
chaliy added a commit that referenced this pull request Oct 8, 2026
Shell diagnostics now read like non-interactive bash:
`SCRIPT: line 2: x: command not found`, `bash: line 1: type: h: not found`
under -c, instead of a bare `bash: `. The name is BASH_SOURCE[0] when a
file is being read (a sourced file names itself), else $0.

- Interpreter messages go through Interpreter::diag_prefix / diag.
- Bundled builtins keep their `bash: name:` / `name:` text; the builtin
  dispatch point rewrites the line start before redirects or streaming
  see it. Usage lines, coreutils-style builtins and execution plans are
  untouched.
- eval syntax errors read `$0: eval: line N:`; failglob, arithmetic and
  write-error messages honor $0.
- `source missing` ends with a newline and reports the missing path;
  `source` with no operand exits 2 with bash's usage line.
- Redirect failures on while/for/if/case report the closing line.
- `set -u` names a bare positional `$2` like bash.
- Child shells (bash -c, scripts by path) get their own name and lines.
- Bash::set_interactive (used by the CLI REPL and Terminal) keeps the
  `bash: msg` form of `bash -i`.

bash-oracle: match 92 -> 98, strict 71 -> 89 of 106 (with #2622) (floors raised).
chaliy added a commit that referenced this pull request Oct 8, 2026
Shell diagnostics now read like non-interactive bash:
`SCRIPT: line 2: x: command not found`, `bash: line 1: type: h: not found`
under -c, instead of a bare `bash: `. The name is BASH_SOURCE[0] when a
file is being read (a sourced file names itself), else $0.

- Interpreter messages go through Interpreter::diag_prefix / diag.
- Bundled builtins keep their `bash: name:` / `name:` text; the builtin
  dispatch point rewrites the line start before redirects or streaming
  see it. Usage lines, coreutils-style builtins and execution plans are
  untouched.
- eval syntax errors read `$0: eval: line N:`; failglob, arithmetic and
  write-error messages honor $0.
- `source missing` ends with a newline and reports the missing path;
  `source` with no operand exits 2 with bash's usage line.
- Redirect failures on while/for/if/case report the closing line.
- `set -u` names a bare positional `$2` like bash.
- Child shells (bash -c, scripts by path) get their own name and lines.
- Bash::set_interactive (used by the CLI REPL and Terminal) keeps the
  `bash: msg` form of `bash -i`.

bash-oracle: match 92 -> 98, strict 71 -> 89 of 106 (with #2622) (floors raised).
chaliy added a commit that referenced this pull request Oct 8, 2026
Shell diagnostics now read like non-interactive bash:
`SCRIPT: line 2: x: command not found`, `bash: line 1: type: h: not found`
under -c, instead of a bare `bash: `. The name is BASH_SOURCE[0] when a
file is being read (a sourced file names itself), else $0.

- Interpreter messages go through Interpreter::diag_prefix / diag.
- Bundled builtins keep their `bash: name:` / `name:` text; the builtin
  dispatch point rewrites the line start before redirects or streaming
  see it. Usage lines, coreutils-style builtins and execution plans are
  untouched.
- eval syntax errors read `$0: eval: line N:`; failglob, arithmetic and
  write-error messages honor $0.
- `source missing` ends with a newline and reports the missing path;
  `source` with no operand exits 2 with bash's usage line.
- Redirect failures on while/for/if/case report the closing line.
- `set -u` names a bare positional `$2` like bash.
- Child shells (bash -c, scripts by path) get their own name and lines.
- Bash::set_interactive (used by the CLI REPL and Terminal) keeps the
  `bash: msg` form of `bash -i`.

bash-oracle: match 92 -> 98, strict 71 -> 89 of 106 (with #2622) (floors raised).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant